#include "plane.h"
#include "mat4.h"

namespace mt
{
	plane::plane() : Data(0.f, 1.f, 0.f, 0.f) {}
	plane::plane(const plane& other) :Data(other.Data) {}
	plane::plane(float _x, float _y, float _z, float _w) : Data(_x, _y, _z, _w) {}
	plane::plane(const vec3f& normal, float d) : Data(normal.X, normal.Y, normal.Z, d) {}
	plane::plane(const vec3f& point1, const vec3f& point2, const vec3f& point3)
	{
		using namespace DirectX;
		XMVECTOR P0 = XMLoadFloat3(&point1.Data);
		XMVECTOR P1 = XMLoadFloat3(&point2.Data);
		XMVECTOR P2 = XMLoadFloat3(&point3.Data);
		XMStoreFloat4(&Data, XMPlaneFromPoints(P0, P1, P2));
	}
	plane::plane(const vec3f& point, const vec3f& normal)
	{
		using namespace DirectX;
		XMVECTOR P = XMLoadFloat3(&point.Data);
		XMVECTOR N = XMLoadFloat3(&normal.Data);
		XMStoreFloat4(&Data, XMPlaneFromPointNormal(P, N));
	}
	plane::plane(const DirectX::XMFLOAT4& v) : Data(v.x, v.y, v.z, v.w) {}
	plane::plane(const float *pArray) : Data(pArray) {}
	plane::plane(DirectX::XMVECTOR& V)
	{
		store(V);
	}

	DirectX::XMVECTOR plane::getXMVector() const
	{
		return DirectX::XMLoadFloat4(&Data);
	}

	void plane::store(DirectX::XMVECTOR& v)
	{
		using namespace DirectX;
		XMStoreFloat4(&Data, v);
	}
	// Comparision operators
	bool plane::operator == (const plane& p) const
	{
		using namespace DirectX;
		XMVECTOR p1 = XMLoadFloat4(&Data);
		XMVECTOR p2 = XMLoadFloat4(&p.Data);
		return XMPlaneEqual(p1, p2);
	}
	bool plane::operator != (const plane& p) const
	{
		using namespace DirectX;
		XMVECTOR p1 = XMLoadFloat4(&Data);
		XMVECTOR p2 = XMLoadFloat4(&p.Data);
		return !XMPlaneEqual(p1, p2);
	}

	// Assignment operators
	plane& plane::operator= (const plane& p) { SND.Normal = p.SND.Normal; SND.D = p.SND.D; return *this; }

	// Properties
	vec3f plane::normal() const { return SND.Normal; }
	void plane::Normal(const vec3f& normal)
	{
		SND.Normal = normal;
	}

	float plane::d() const { return SND.D; }
	void plane::d(float d) { SND.D = d; }

	// plane operations
	void plane::Normalize()
	{
		using namespace DirectX;
		XMVECTOR p = XMLoadFloat4(&Data);
		XMStoreFloat4(&Data, XMPlaneNormalize(p));
	}
	void plane::Normalize(plane& result) const
	{
		using namespace DirectX;
		XMVECTOR p = XMLoadFloat4(&Data);
		XMStoreFloat4(&result.Data, XMPlaneNormalize(p));
	}

	float plane::Dot(const DirectX::XMFLOAT4& v) const
	{
		using namespace DirectX;
		XMVECTOR v1 = XMLoadFloat4(&v);
		XMVECTOR v2 = getXMVector();
		return XMVectorGetX(XMPlaneDot(v1, v2));
	}

	float plane::DotCoordinate(const vec3f& position) const
	{
		using namespace DirectX;
		XMVECTOR v0 = position.getXMVector();
		XMVECTOR p = getXMVector();
		return XMVectorGetX(XMPlaneDotCoord(p, v0));
	}

	float plane::DotNormal(const vec3f& normal) const
	{
		using namespace DirectX;
		XMVECTOR n0 = normal.getXMVector();
		XMVECTOR p = getXMVector();
		return XMVectorGetX(XMPlaneDotNormal(p, n0));
	}

	vec3f plane::getMemberPoint()const
	{
		return SND.Normal*(-SND.D);
	}

	void plane::setPlane(const vec3f& point, const vec3f& nvector)
	{
		SND.Normal = nvector;
		recalculateD(point);
	}

	//! Recalculates the distance from origin by applying a new member point to the plane.
	void plane::recalculateD(const vec3f& MPoint)
	{
		SND.D = -MPoint.dotProduct(SND.Normal);
	}

	// Static functions
	void plane::Transform(plane& result, const plane& plane0, const DirectX::XMFLOAT4X4& M)
	{
		using namespace DirectX;
		XMVECTOR p = XMLoadFloat4(&plane0.Data);
		XMMATRIX m0 = XMLoadFloat4x4(&M);
		XMStoreFloat4(&result.Data, XMPlaneTransform(p, m0));
	}

	void plane::Transform(plane& result, const plane& plane0, const mat4& m)
	{
		using namespace DirectX;
		XMVECTOR p = XMLoadFloat4(&plane0.Data);
		XMMATRIX m0 = XMLoadFloat4x4(&(m.Data));
		XMStoreFloat4(&result.Data, XMPlaneTransform(p, m0));
	}

	plane plane::Transform(const plane& pl, const DirectX::XMFLOAT4X4& M)
	{
		using namespace DirectX;
		XMVECTOR p = XMLoadFloat4(&pl.Data);
		XMMATRIX m0 = XMLoadFloat4x4(&M);

		plane result;
		XMStoreFloat4(&result.Data, XMPlaneTransform(p, m0));
		return result;
	}

	void plane::Transform(const plane& plane0, const quaternion& rotation, plane& result)
	{
		using namespace DirectX;
		XMVECTOR p = XMLoadFloat4(&plane0.Data);
		XMVECTOR q = XMLoadFloat4(&rotation.Data);
		XMVECTOR X = XMVector3Rotate(p, q);
		X = XMVectorSelect(p, X, g_XMSelect1110); // result.d = plane.d
		XMStoreFloat4(&result.Data, X);
	}

	plane plane::Transform(const plane& plane0, const quaternion& rotation)
	{
		using namespace DirectX;
		XMVECTOR p = XMLoadFloat4(&plane0.Data);
		XMVECTOR q = XMLoadFloat4(&rotation.Data);
		XMVECTOR X = XMVector3Rotate(p, q);
		X = XMVectorSelect(p, X, g_XMSelect1110); // result.d = plane.d

		plane result;
		XMStoreFloat4(&result.Data, X);
		return result;
	}
}